// Copyright (C) 2012 Volkov Zachar
// This file is part of the "zAxis Game Engine".
// For conditions of distribution and use, see copyright notice zx_Common.hpp

#include "zx_Video.hpp"
#include "gl/glew.h"

namespace zx {

static const char* _shared_lib = 
{
     "                                                                                    \n"
     "#extension GL_ARB_texture_rectangle: enable                                         \n"
     "#define oPosintSize gl_PointSize  // used as result of vertex-shader                \n"
     "#define oPosition   gl_Position   // used as result of vertex-shader                \n"
     "#define iFragCoord  gl_FragCoord                                                    \n"
     "#define oFragColor  gl_FragColor  // used as result of fragment-shader              \n"
     "#define oFragDepth  gl_FragDepth  // used as result of fragment-shader              \n"
     "                                                                                    \n"
     "// a attributes of vertex                                                           \n"
     "attribute vec4 vaPosition; // a vertex position                                     \n"
     "attribute vec4 vaDiffuse;  // a vertex diffuse color                                \n"
     "attribute vec2 vaTexCoord; // a vertex texture coordinates                          \n"
     "attribute vec4 vaSpecular; // a vertex specular color                               \n"
     "attribute vec3 vaNormal;   // a vertex normal                                       \n"
     "attribute vec3 vaTangent;  // a vertex tangent                                      \n"
     "attribute vec3 vaBinormal; // a vertex binormal                                     \n"
     "                                                                                    \n"
     "// a default varying-parameters                                                     \n"
     "varying vec4 vPosition;      //                                                     \n"
     "varying vec4 vWorldPos;      //                                                     \n"
     "varying vec2 vTexCoord;      // a vertex texture coordinates                        \n"
     "varying vec4 vDiffuse;       // a vertex diffuse color                              \n"
     "varying vec4 vSpecular;      // a vertex specular color                             \n"
     "varying vec3 vNormal;        // a vertex normal                                     \n"
     "varying vec3 vTangent;       // a vertex tangent                                    \n"
     "varying vec3 vBinormal;      // a vertex binormal                                   \n"
     "                                                                                    \n"
     "// a structures                                                                     \n"
     "struct Sampler1D { sampler1D texture; vec3 size; vec3 invsize; };                   \n"
     "struct Sampler2D { sampler2D texture; vec3 size; vec3 invsize; };                   \n"
     "struct Sampler3D { sampler3D texture; vec3 size; vec3 invsize; };                   \n"
     "struct SamplerRect { sampler2DRect texture; vec3 size; vec3 invsize; };             \n"
     "struct SamplerCube { samplerCube texture; vec3 size; vec3 invsize; };               \n"
     "                                                                                    \n"
     "// a default uniforms                                                               \n"
     "uniform mat4 uProjMatrix;                 // a projection matrix                    \n"
     "uniform mat4 uInverseProjMatrix;          // a inverse projection matrix            \n"
     "uniform mat4 uWorldMatrix;                // a world matrix                         \n"
     "uniform mat4 uInverseWorldMatrix;         // a inverse world matrix                 \n"
     "uniform mat4 uViewMatrix;                 // a view matrix                          \n"
     "uniform mat4 uInverseViewMatrix;          // a inverse view matrix                  \n"
     "uniform mat4 uWorldViewMatrix;            // a world-view matrix                    \n"
     "uniform mat4 uInverseWorldViewMatrix;     // a inverse world-view matrix            \n"
     "uniform mat4 uWorldViewProjMatrix;        // a world-view-projection matrix         \n"
     "uniform mat4 uInverseWorldViewProjMatrix; // a inverse world-view-projection matrix \n"
     "uniform mat3 uNormalMatrix;               // a matrix for normal                    \n"
     "uniform mat3 uInverseNormalMatrix;        // a inverse matrix for normal            \n"
     "uniform vec2 uViewportSize;               // x = width, y = height                  \n"
     "uniform vec4 uDepthRange;                 // x = near, y = far, z = far-near, w = 1.0/(far-near)\n"
};

static const char* _entry_point = 
{
     "\n"
     "#define main_%s main // define entry point an main \n"
     "#define %s_SHADER    // shader type indicator (VERTEX_SHADER or FRAGMENT_SHADER)\n"
};


//----------------------------------------------------------------------------//
// Program::ActiveUniform
//----------------------------------------------------------------------------//

Program::ActiveUniform::ActiveUniform( const String& _name, 
                                       int _index, 
                                       UniformType _type, 
                                       TextureType _sampler_type ) : name( _name )
                                                                   , index( _index )
                                                                   , type( _type )
                                                                   , sampler_type( _sampler_type )
                                                                   , processed( false ) 
{
}

//----------------------------------------------------------------------------//
// Program
//----------------------------------------------------------------------------//

Program::Program( const String& _prog ) : m_program( 0 )
                                        , m_vertex_shader( 0 )
                                        , m_fragment_shader( 0 )
                                        , m_count_samplers( 0 )
                                        , m_valid( false )
{
     // create handles
     m_vertex_shader = glCreateShaderObjectARB( GL_VERTEX_SHADER_ARB );
     m_fragment_shader = glCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB );
     m_program = glCreateProgramObjectARB();

     // bind vertex attributes
     glBindAttribLocationARB( m_program, VA_POSITION, "vaPosition" );
     glBindAttribLocationARB( m_program, VA_NORMAL, "vaNormal" );
     glBindAttribLocationARB( m_program, VA_TANGENT, "vaTangent" );
     glBindAttribLocationARB( m_program, VA_BINORMAL, "vaBinormal" );
     glBindAttribLocationARB( m_program, VA_TEXCOORD, "vaTexCoord" );
     glBindAttribLocationARB( m_program, VA_DIFFUSE, "vaDiffuse" );
     glBindAttribLocationARB( m_program, VA_SPECULAR, "vaSpecular" );

     if( !_prog.empty() )
     {
          Compile( _prog );
     }
     else
     {
          _AddError( "Program not was compiled" );
          _CreateLog();
          _CreateDetailedLog();

          for( uint i = 0; i < DU___COUNT; ++ i )
          {
               m_default_uniforms[ i ] = -1;
          }
     }
}
//----------------------------------------------------------------------------//
Program::~Program( void )
{
     glDeleteObjectARB( m_vertex_shader );
     glDeleteObjectARB( m_fragment_shader );
     glDeleteObjectARB( m_program );
}
//----------------------------------------------------------------------------//
void Program::_Bind( void )
{
     VideoDriver::Instance()._BindProgram( this );
}
//----------------------------------------------------------------------------//
void Program::_ReadActiveUniforms( void )
{
     GLint _count_uniforms, _max_name_length, _length, _size, _location;
     GLenum _type;
     ActiveUniform _ac;
     char _name[512];

     glGetObjectParameterivARB( m_program, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &_count_uniforms );
     glGetObjectParameterivARB( m_program, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, &_max_name_length );
          
     assert( _max_name_length < sizeof( _name ) );
     
     for( int i = 0; i < _count_uniforms; ++ i )
     {
          glGetActiveUniformARB( m_program, i, _max_name_length, &_length, &_size, &_type, _name );
          _name[_length] = 0;
          _location = glGetUniformLocationARB( m_program, _name );

          switch( _type )
          {
               case GL_SAMPLER_1D_ARB      : _ac = ActiveUniform( _name, _location, UT_SAMPLER, TT_1D ); break;
               case GL_SAMPLER_2D_ARB      : _ac = ActiveUniform( _name, _location, UT_SAMPLER, TT_2D ); break;
               case GL_SAMPLER_3D_ARB      : _ac = ActiveUniform( _name, _location, UT_SAMPLER, TT_3D ); break;
               case GL_SAMPLER_2D_RECT_ARB : _ac = ActiveUniform( _name, _location, UT_SAMPLER, TT_RECT ); break;
               case GL_SAMPLER_CUBE_ARB    : _ac = ActiveUniform( _name, _location, UT_SAMPLER, TT_CUBE ); break;
               case GL_FLOAT               : _ac = ActiveUniform( _name, _location, UT_FLOAT ); break;
               case GL_FLOAT_VEC2_ARB      : _ac = ActiveUniform( _name, _location, UT_VECTOR2 ); break;
               case GL_FLOAT_VEC3_ARB      : _ac = ActiveUniform( _name, _location, UT_VECTOR3 ); break;
               case GL_FLOAT_VEC4_ARB      : _ac = ActiveUniform( _name, _location, UT_VECTOR4 ); break;
               case GL_FLOAT_MAT3_ARB      : _ac = ActiveUniform( _name, _location, UT_MATRIX3 ); break;
               case GL_FLOAT_MAT4_ARB      : _ac = ActiveUniform( _name, _location, UT_MATRIX4 ); break;
               default                     : _ac = ActiveUniform( _name, -1, (UniformType)_type ); break; // unsupported type
          };

          m_active_uniforms.push_back( _ac );
     }
}
//----------------------------------------------------------------------------//
int Program::_GetActiveUniform( const String& _name )
{
     for( uint i = 0; i < m_active_uniforms.size(); ++ i )
     {
          if( m_active_uniforms[i].name == _name )
          {
               return i;
          }
     }

     return -1;
}
//----------------------------------------------------------------------------//
String Program::_ExtractSamplerName( const String& _name )
{
     uint _pos = _name.find_last_of( "." );
     if( _pos == String::npos || _name.substr( _pos ) != ".texture" )
     {
          return "";
     }

     return _name.substr( 0, _pos );
}
//----------------------------------------------------------------------------//
void Program::_InitUniformTable( void )
{
 // BIND

     // 1. detect samplers (texture+size+invsize)
     for( uint i = 0; i < m_active_uniforms.size(); ++ i )
     {
          if( m_active_uniforms[i].index < 0 )
          {
               m_valid = false;
               m_active_uniforms[i].processed = true;
               _AddError( Format( "Invalid uniform: '%s' with type 0x%04x", m_active_uniforms[i].name.c_str(), m_active_uniforms[i].type ) );
               continue;
          }
           
          if( m_active_uniforms[i].type == UT_SAMPLER )
          {
               int _sampler_size_loc = -1, _sampler_invsize_loc = -1;

               String _sampler = _ExtractSamplerName( m_active_uniforms[i].name );
               if( _sampler.empty() )
               {
                    _sampler = m_active_uniforms[i].name; 
               }
               else
               { 
                    int _sampler_size_idx = _GetActiveUniform( _sampler + ".size" );
                    int _sampler_invsize_idx = _GetActiveUniform( _sampler + ".invsize" );

                    if( _sampler_size_idx >= 0 )
                    {
                         if( m_active_uniforms[ _sampler_size_idx ].type != UT_VECTOR3 )
                         {
                              _AddError( Format( "Uniform '%s' is not vec3", m_active_uniforms[ _sampler_size_idx ].name.c_str() ) );
                              m_valid = false;
                         }

                         _sampler_size_loc = m_active_uniforms[ _sampler_size_idx ].index;
                         m_active_uniforms[ _sampler_size_idx ].processed = true; 
                    }

                    if( _sampler_invsize_idx >= 0 )
                    {
                         if( m_active_uniforms[ _sampler_invsize_idx ].type != UT_VECTOR3 )
                         {
                              _AddError( Format( "Uniform '%s' is not vec3", m_active_uniforms[ _sampler_invsize_idx ].name.c_str() ) );
                              m_valid = false;
                         }

                         _sampler_invsize_loc = m_active_uniforms[ _sampler_invsize_idx ].index;
                         m_active_uniforms[ _sampler_invsize_idx ].processed = true; 
                    }
               }
               
               m_indices[ _sampler ] = m_uniforms.size();
               m_uniforms.push_back( new Uniform( _sampler, UT_SAMPLER, m_active_uniforms[i].index, m_count_samplers, _sampler_size_loc, _sampler_invsize_loc, m_active_uniforms[i].sampler_type ) );
               m_active_uniforms[i].processed = true;

               glUniform1iARB( m_active_uniforms[i].index, ++ m_count_samplers );
          }
     }

     // 2. add other uniforms
     for( uint i = 0; i < m_active_uniforms.size(); ++ i )
     {
          if( m_active_uniforms[i].processed )
          {
               continue;
          }

          m_active_uniforms[i].processed = true;

          if( m_active_uniforms[i].index < 0 )
          {
               m_valid = false;
               _AddError( Format( "Invalid uniform: '%s' with type 0x%04x", m_active_uniforms[i].name.c_str(), m_active_uniforms[i].type ) );
               continue;
          }

          assert( m_active_uniforms[i].type >= UT_FLOAT && m_active_uniforms[i].type <= UT_MATRIX4 );

          m_indices[ m_active_uniforms[i].name ] = m_uniforms.size();
          m_uniforms.push_back( new Uniform( m_active_uniforms[i].name, m_active_uniforms[i].type, m_active_uniforms[i].index ) );
     }
}
//----------------------------------------------------------------------------//
void Program::_InitDefaultUniforms( void )
{
     for( uint i = 0; i < DU___COUNT; ++ i )
     {

          m_default_uniforms[ i ] = GetIndex( DefaultUniformNames[i] );
     }
}
//----------------------------------------------------------------------------//
void Program::_ReadVertexFormat( void )
{
     GLint _count_attribs, _max_name_length, _length, _size;
     GLenum _type;
     char _name[512];
     String _a_name;

     glGetObjectParameterivARB( m_program, GL_OBJECT_ACTIVE_ATTRIBUTES_ARB, &_count_attribs );
     glGetObjectParameterivARB( m_program, GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB, &_max_name_length );
     assert( _max_name_length < sizeof( _name ) );

     for( int i = 0; i < _count_attribs; ++ i )
     {
          glGetActiveAttribARB( m_program, i, _max_name_length, &_length, &_size, &_type, _name );
          _name[_length] = 0;
          _a_name = _name;

          if( _a_name.find( "vaPosition" ) == 0 ){ m_vertex_format |= ( 1 << VA_POSITION ); }
          else if( _a_name.find( "vaNormal" ) == 0 ){ m_vertex_format |= ( 1 << VA_NORMAL ); }
          else if( _a_name.find( "vaBinormal" ) == 0 ){ m_vertex_format |= ( 1 << VA_BINORMAL ); }
          else if( _a_name.find( "vaTangent" ) == 0 ){ m_vertex_format |= ( 1 << VA_TANGENT ); }
          else if( _a_name.find( "vaTexCoord" ) == 0 ){ m_vertex_format |= ( 1 << VA_TEXCOORD ); }
          else if( _a_name.find( "vaDiffuse" ) == 0 ){ m_vertex_format |= ( 1 << VA_DIFFUSE ); }
          else if( _a_name.find( "vaSpecular" ) == 0 ){ m_vertex_format |= ( 1 << VA_SPECULAR ); }
     }
}
//----------------------------------------------------------------------------//
void Program::_ReadBuiltinCompilerLog( void )
{
     GLint _length, _written;
     glGetObjectParameterivARB( m_program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &_length );
     if( _length > 1 )
     {
          m_builtin_compiler_log.resize( _length - 1 );
          glGetInfoLogARB( m_program, _length, &_written, const_cast< GLcharARB* >( m_builtin_compiler_log.c_str() ) );
          m_builtin_compiler_log += "\n";
     }
}
//----------------------------------------------------------------------------//
void Program::_Reset( void )
{
     m_program_src_with_lines.clear();
     m_errors.clear();
     m_builtin_compiler_log.clear();
     m_log.clear();
     m_detailed_log.clear();
     m_program_src.clear();
     m_vertex_format = 0;
     m_valid = false;
     m_count_samplers = 0;
     m_active_uniforms.clear();
     m_indices.clear();
     m_uniforms.clear();

     for( uint i = 0; i < DU___COUNT; ++ i )
     {
          m_default_uniforms[ i ] = -1;
     }
}
//----------------------------------------------------------------------------//
void Program::_AddError( const String& _error )
{
     m_errors += "\n    Error: " + _error;
}
//----------------------------------------------------------------------------//
void Program::SetValue( int _index, const UniformValue& _value )
{
     if( _index >= 0 && _index < (int)m_uniforms.size() )
     {
          m_uniforms[ _index ] -> SetValue( _value );
     }
}
//----------------------------------------------------------------------------//
const UniformValue& Program::GetValue( int _index ) const
{
     if( _index >= 0 && _index < (int)m_uniforms.size() )
     {
          return m_uniforms[ _index ] -> GetValue();
     }

     return UniformValue::Null();
}
//----------------------------------------------------------------------------//
bool Program::Compile( const String& _src )
{
     _Reset();

     // prepare source
     m_program_src = "\n" + _src + "\n";
     String _src_with_lib = _shared_lib + m_program_src;
     //String _vs_with_lib_and_hidden_code = _src_with_lib + String( _main_vs );
     //String _fs_with_lib_and_hidden_code = _src_with_lib + String( _main_fs );
     String _vs_with_lib_and_hidden_code = Format( _entry_point, "vs", "VERTEX" ) + _src_with_lib;
     String _fs_with_lib_and_hidden_code = Format( _entry_point, "fs", "FRAGMENT" ) + _src_with_lib;

     const char* _vss = _vs_with_lib_and_hidden_code.c_str(); 
     const char* _fss = _fs_with_lib_and_hidden_code.c_str(); 

     // load sources
     glShaderSourceARB( m_vertex_shader, 1, &_vss, 0 );
     glShaderSourceARB( m_fragment_shader, 1, &_fss, 0 );

     // compile shaders
     GLint _state;
     glCompileShaderARB( m_vertex_shader );
     glCompileShaderARB( m_fragment_shader );
     glGetObjectParameterivARB( m_vertex_shader, GL_OBJECT_COMPILE_STATUS_ARB, &_state );
     glGetObjectParameterivARB( m_fragment_shader, GL_OBJECT_COMPILE_STATUS_ARB, &_state );

     // attach shaders
     glAttachObjectARB( m_program, m_vertex_shader );
     glAttachObjectARB( m_program, m_fragment_shader );

     // link program
     glLinkProgramARB( m_program );

     // validate program
     glGetObjectParameterivARB( m_program, GL_OBJECT_LINK_STATUS_ARB, &_state );
     if( _state == 0 )
     {
          _AddError( "Could not compile or link gpu program" );
          m_valid = false;
     }
     else
     {
          m_valid = true;
     }

     _Bind();
     _ReadActiveUniforms();
     _InitUniformTable();
     _InitDefaultUniforms();
     _ReadVertexFormat();
     _ReadBuiltinCompilerLog();
     _PrepareProgramSourceForLog();
     _CreateLog();
     _CreateDetailedLog();

     return m_valid;
}
//----------------------------------------------------------------------------//
void Program::_PrepareProgramSourceForLog( void )
{
     uint _line = 1;

     m_program_src_with_lines += "\n    ---- entry point macros ----";
     m_program_src_with_lines += "\n    1   : ";

     for( uint i = 0; _entry_point[i] != '\0' ; ++ i )
     {
          if( _entry_point[i] == '\n' )
          {
               ++ _line;
               m_program_src_with_lines += Format( "\n    %u ", _line );
               if( _line < 10 ) { m_program_src_with_lines += " "; }
               if( _line < 100 ) { m_program_src_with_lines += " "; }
               m_program_src_with_lines += ": ";
          }
          else
          {
               m_program_src_with_lines += _entry_point[i];
          }
     }

     m_program_src_with_lines += "\n    ---- shared library ----";

     for( uint i = 0; _shared_lib[i] != '\0' ; ++ i )
     {
          if( _shared_lib[i] == '\n' )
          {
               ++ _line;
               m_program_src_with_lines += Format( "\n    %u ", _line );
               if( _line < 10 ) { m_program_src_with_lines += " "; }
               if( _line < 100 ) { m_program_src_with_lines += " "; }
               m_program_src_with_lines += ": ";
          }
          else
          {
               m_program_src_with_lines += _shared_lib[i];
          }
     }

     m_program_src_with_lines += "\n    ---- program source ----";

     for( uint i = 0; i < m_program_src.length(); ++ i )
     {
          if( m_program_src[i] == '\n' )
          {
               ++ _line;
               m_program_src_with_lines += Format( "\n    %u ", _line );
               if( _line < 10 ) { m_program_src_with_lines += " "; }
               if( _line < 100 ) { m_program_src_with_lines += " "; }
               m_program_src_with_lines += ": ";
          }
          else
          {
               m_program_src_with_lines += m_program_src[i];
          }
     }

     m_program_src_with_lines += "\n";
}
//----------------------------------------------------------------------------//
int Program::GetIndex( const String& _name ) const
{     
     map< String, uint >::const_iterator _iter = m_indices.find( _name );

     if( _iter != m_indices.end() )
     {
          return _iter -> second;
     }

     return -1;
}
//----------------------------------------------------------------------------//
UniformPtr Program::GetUniform( int _index ) const
{
     if( _index >= 0 && _index < (int)m_uniforms.size() )
     {
          return m_uniforms[ _index ];
     }

     return 0;
}
//----------------------------------------------------------------------------//
void Program::_CreateLog( void )
{
     if( !m_errors.empty() )
     {
          m_errors += "\n";
          m_log += "\n  ** Errors:" + m_errors; 
     }

     if( !m_builtin_compiler_log.empty() )
     {
          m_log += "\n  ** Builtin-compiler log:\n    "; 

          for( uint i = 0, line = 1; i < m_builtin_compiler_log.length(); ++ i )
          {
               m_log += m_builtin_compiler_log[i];

               if( m_builtin_compiler_log[i] == '\n' )
               {
                    m_log += "    "; 
               }
          }
     }
}
//----------------------------------------------------------------------------//
void Program::_CreateDetailedLog( void )
{
     m_detailed_log += m_log + "\n  ** Source:" + m_program_src_with_lines;
}
//----------------------------------------------------------------------------//
bool Program::LoadFromFile( const String& _path )
{
     String _source;
     StringVector _errors;

     if( _ParseFile( _source, _path, "", _errors ) )
     {
          return Compile( _source );     
     }

     _Reset();

     for( uint i = 0; i < _errors.size(); ++ i )
     {
          _AddError( _errors[i] );
     }

     _AddError( "Program not was compiled" );

     _CreateLog();
     _CreateDetailedLog();

     return false;
}
//----------------------------------------------------------------------------//
bool Program::_ParseFile( String& _dst, const String& _filename, const String& _parent, StringVector& _errors, int _depth )
{
     // check recursion depth
     if( _depth > 64 )
     {
          _errors.push_back( Format( "Too large recursion depth = %d in file '%s'", _depth, _filename.c_str() ) );
          return false;
     }

     // open & read file
     if( _filename.empty() )
     {
          _errors.push_back( "Empty filename" );
          return false;
     }

     DataStreamPtr _file = FileSystem::OpenFileFromSearchDirectory( _filename, FileSystem::PathTo( _parent ) );
     
     if( !_file )
     {
          _errors.push_back( Format( "Could not open file '%s'", _filename.c_str() ) );
          return false;
     }

     String _srcbuff = _file -> ReadString();

     if( _srcbuff.empty() )
     {
          return true; // empty file
     }

     //parsing
     //_dst += "\n// -- begin file '" + _filename + "'\n";


     const char *_src = _srcbuff.c_str();
     
     uint y = 1;
     
     while( _src[0] != 0 )
     {
          if( _src[0] == '\n' ) { ++ y; } // update line counter
          if( _src[0] == '\r' ) { ++ _src; continue; } //? \n\r ? \r\n ? \n ? \r ?

          // skip comment
          if( _src[0] == '/' )
          {
               _dst += "/";
               ++ _src;
               if( _src[0] == '/' ) // comment
               {
                    _dst += "/";
                    ++ _src; // skip //
                    while( _src[0] != 0 ) 
                    { 
                         if( _src[0] != '\r' ) { _dst += _src[0]; }

                         if( _src[0] == '\n' )
                         {
                              ++ _src;
                              ++ y;
                              break;
                         }

                         ++ _src;
                    }
               }
               else if( _src[0] == '*' ) // multiline comment
               {
                    _dst += "*";
                    ++ _src; // skip /*
                    while( _src[0] != 0 ) 
                    { 
                         if( _src[0] != '\r' ) { _dst += _src[0]; }
                         if( _src[0] == '\n' ) { ++ y; }

                         if( _src[0] == '*' && _src[1] == '/' )
                         {
                              _src += 2; // skip */
                              _dst += "/";
                              break;
                         }

                         ++ _src;
                    }
               }
          }
          else if( _src[0] == '#' )
          {
               if( _srcbuff.substr( _src - _srcbuff.c_str(), 8 ) == "#include" )
               {
                    _src += 8; // skip #include

                    String _include;

                    // skip white space
                    while( _src[0] != 0 && (_src[0] == ' ' || _src[0] == '\t') ) { ++ _src; }

                    // check EOF
                    if( _src[0] == 0 )
                    {
                         _errors.push_back( Format( "%s(%u): Unexpected end of file", _file -> GetName().c_str(), y ) );
                         return false;
                    }

                    // check string constant
                    if( _src[0] != '\"' )
                    {
                         _errors.push_back( Format( "%s(%u): Expected string constant was not found", _file -> GetName().c_str(), y ) );
                         return false;
                    }

                    // read string
                    ++ _src; // skip "
                    while( _src[0] != 0 && _src[0] != '\"' )
                    {
                         if( _src[0] == '\n' || _src[0] == '\r' )
                         {
                              _errors.push_back( Format( "%s(%u): New line in string constant", _file -> GetName().c_str(), y ) );
                              return false;
                         }

                         if( _src[0] == '\\' && _src[1] == '\"' )
                         {
                              _src += 2; // skip \"
                              _include += "\"";
                              continue;
                         }

                         if( _src[0] == '\\' && _src[1] == '\\' )
                         {
                              _src += 2; // skip '\\' 
                              _include += "\\";
                              continue;
                         }

                         _include += _src[0];
                         ++ _src;
                    }

                    // check EOF
                    if( _src[0] == 0 )
                    {
                         _errors.push_back( Format( "%s(%u): End of file in string constant", _file -> GetName().c_str(), y ) );
                         return false;
                    }

                    ++ _src; // skip "

                    // include file
                    if( !_include.empty() )
                    {
                         if( !_ParseFile( _dst, _include, _file -> GetName(), _errors, _depth + 1 ) )
                         {
                              _errors.push_back( Format( "%s(%u): Failed include file '%s'", _file -> GetName().c_str(), y, _include.c_str() ) );
                              return false;
                         }
                         _dst += "\n";
                    }
               }
               else
               {
                    _dst += _src[0];
                    ++ _src;
               }
          }
          else
          {
               if( _src[0] != '\r' ){ _dst += _src[0]; } 
               ++ _src;
          }
     }

     //_dst += "\n// -- end file '" + _filename + "'\n";

     return true;
}

//----------------------------------------------------------------------------//
//----------------------------------------------------------------------------//

} // namespace zx
